<!doctype html>
<html>
    <head>
        <title>HT for Web OBJ手册</title>
        <meta charset="UTF-8">    
        <meta name="viewport" content="user-scalable=yes, width=1024">
        <style type="text/css"> 
            h1, h2, h3, h4, h5, h6, p, blockquote {
                margin: 0;
                padding: 0;
            }
            body {
                font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
                font-size: 13px;
                line-height: 18px;
                color: #737373;
                background-color: white; 
                margin: 10px 13px 10px 13px;
            }
            table {
                margin: 10px 0 15px 0;
                border-collapse: collapse;
            }
            td,th {	
                border: 1px solid #ddd;
                padding: 3px 10px;
            }
            th {
                padding: 5px 10px;	
            }
            a {
                color: #0069d6;
            }
            a:hover {
                color: #0050a3;
                text-decoration: none;
            }
            a img {
                border: none;
            }
            p {
                margin-bottom: 9px;
            }
            h1, h2, h3, h4, h5, h6 {
                color: #404040;
                line-height: 36px;
            }
            h1 {
                margin-bottom: 18px;
                font-size: 30px;
            }
            h2 {
                font-size: 24px;
            }
            h3 {
                font-size: 18px;
            }
            h4 {
                font-size: 16px;
            }
            h5 {
                font-size: 14px;
            }
            h6 {
                font-size: 13px;
            }
            hr {
                margin: 0 0 19px;
                border: 0;
                border-bottom: 1px solid #ccc;
            }
            blockquote {
                padding: 13px 13px 21px 15px;
                margin-bottom: 18px;
                font-family:georgia,serif;
                font-style: italic;
            }
            blockquote:before {
                content:"\201C";
                font-size:40px;
                margin-left:-10px;
                font-family:georgia,serif;
                color:#eee;
            }
            blockquote p {
                font-size: 14px;
                font-weight: 300;
                line-height: 18px;
                margin-bottom: 0;
                font-style: italic;
            }
            code, pre {
                font-family: Monaco, Andale Mono, Courier New, monospace;
            }
            code {
                background-color: #fee9cc;
                color: rgba(0, 0, 0, 0.75);
                padding: 1px 3px;
                font-size: 12px;
                -webkit-border-radius: 3px;
                -moz-border-radius: 3px;
                border-radius: 3px;
            }
            pre {
                display: block;
                padding: 14px;
                margin: 0 0 18px;
                line-height: 16px;
                font-size: 11px;
                border: 1px solid #d9d9d9;
                white-space: pre-wrap;
                word-wrap: break-word;
            }
            pre code {
                background-color: #fff;
                color:#737373;
                font-size: 11px;
                padding: 0;
            }
            sup {
                font-size: 0.83em;
                vertical-align: super;
                line-height: 0;
            }
            * {
                -webkit-print-color-adjust: exact;
            }
            @media screen and (min-width: 914px) {
                body {
                    width: 854px;
                    margin:10px auto;
                }
            }
            @media print {
                body,code,pre code,h1,h2,h3,h4,h5,h6 {
                    color: black;
                }
                table, pre {
                    page-break-inside: avoid;
                }
            }                        
            iframe{
                width: 100%;
                border: 1px solid #34495E;
                margin: 0;                
            }            
            .logo{
                vertical-align: middle;
            }

        </style>        
        <script>
            function init() {
                var logoSrc = '',
                    logos = document.querySelectorAll('.logo'),
                    i = 0;
                for (; i < logos.length; i++) {
                    logos[i].src = logoSrc;
                }
                
                var iframes = document.querySelectorAll('iframe'),
                    func = function(){};
                for (i=0; i < iframes.length; i++) {
                    var iframe = iframes[i];
                    
                    // a small hack to make it work on android
                    iframe.ontouchstart = func;
                    
                    var div = document.createElement('div');
                    div.innerHTML = '<a href="' + iframe.src + '" target="_blank">独立运行→</a>';
                    iframe.parentNode.insertBefore(div, iframe);
                }   
            }
        </script>    
    </head>
    <body onload="init();">
        <a href="http://www.hightopo.com"><img class="logo"></a>HT for Web OBJ手册
        <hr style="margin: 1px 0 20px 0">
<p>索引</p>

<ul><li><a href="#ref_overview">概述</a></li><li><a href="#ref_objformat">OBJ格式</a></li><li><a href="#ref_parseobj">解析OBJ</a></li><li><a href="#ref_importmodel">导入模型</a>    <ul><li><a href="#ref_ajax">AJAX加载</a></li><li><a href="#ref_loadobj">loadObj函数</a></li><li><a href="#ref_string">字符串嵌入</a></li></ul></li><li><a href="#ref_doorwindow">门窗应用</a></li></ul>

<hr/>

<div id="ref_overview"></div>

<h2>概述</h2>

<p><code>HT for 3D Web</code>预定义了多种三维模型，并可通过<a href="../../plugin/modeling/ht-modeling-guide.html">建模手册</a>介绍的<code>API</code>方式构建更多样式的模型，
同时<code>HT</code>还提供了导入<a href="http://en.wikipedia.org/wiki/Wavefront_.obj_file">OBJ</a>的<code>3D</code>模型格式文件的功能。</p>

<p>导入<code>OBJ</code>格式功能需要引入<code>ht-obj.js</code>的插件扩展包，本手册的大部分例子由于需要读取<code>OBJ</code>文件，浏览器存在跨域安全的限制，
因此需要通过<code>Web</code>方式发布来阅读本手册，或者修改浏览器的参数，例如对于<code>Chrome</code>浏览器可通过增加
<a href="http://www.chrome-allow-file-access-from-file.com/">--allow-file-access-from-files</a>的启动参数。</p>

<p><iframe src="examples/example_path.html" style="height:400px"></iframe></p>

<div id="ref_objformat"></div>

<h2>OBJ格式</h2>

<p><a href="http://en.wikipedia.org/wiki/Wavefront_.obj_file">OBJ</a> 是一种<code>3D</code>模型文件格式，几乎所有主流<code>3D</code>建模工具，
如<code>Blender</code>、<code>3ds Max</code>和<code>Maya</code>都支持<code>OBJ</code>格式的导出。</p>

<p><code>OBJ</code>文件一般以<code>.obj</code>后缀名标示，描述的是模型顶点、面以及贴图坐标等几何模型相关信息；而模型的贴图图片以及颜色等材质信息，
则由另外的<code>MTL</code>材质文件描述，一般以<code>.mtl</code>后缀名标示。</p>

<p><code>OBJ</code> 文件示例片段如下，<code>v</code>代表顶点信息，<code>f</code>代表面信息，<code>usemtl</code>代表以下面描述模型都将采用外部<code>MTL</code>文件描述的<code>material3</code>材质信息：</p>

<pre><code>v 1.187283 0.016532 0.652852
v 1.187283 0.001827 1.045301
v 1.187283 0.155480 0.618752
v 1.187283 0.106104 1.046487
v 1.187283 0.330175 0.640612
v 1.187283 0.209969 1.085557
v 1.186590 1.499776 1.191882
usemtl material3
f 9918 9919 9920 9921
f 9919 9922 9923 9920
f 9922 9924 9925 9923
f 9924 9926 9927 9925
f 9926 9928 9929 9927
f 9928 9930 9931 9929</code></pre>

<p><code>MTL</code>文件示例片段如下，材质<code>material3</code>透明度<code>d</code>为<code>0.5</code>，<code>kd</code>代表<code>diffuse</code>颜色为<code>[0.58 0.58 0.58]</code>，贴图路径为<code>/SmokeAlarm.jpg</code></p>

<pre><code>newmtl material3
    d 0.5
    Kd 0.58 0.58 0.588
    map_Kd /SmokeAlarm.jpg</code></pre>

<div id="ref_parseobj"></div>

<h2>解析OBJ</h2>

<p><code>ht.Default.parseObj(objText, mtlText, params)</code>函数用于解析<code>obj</code>和<code>mtl</code>文件，
解析后返回的<code>map</code>结构<code>json</code>对象中，每个材质名对应一个模型信息，
模型信息格式为<a href="../../plugin/modeling/ht-modeling-guide.html#ref_register">建模手册</a>介绍的<code>HT</code>自定义的模型格式标准。</p>

<ul><li><code>objText</code>：<code>OBJ</code>格式的文本内容</li><li><code>mtlText</code>：<code>MTL</code>格式的文本内容，无材质信息也可传入<code>null</code></li><li><code>params</code>：<code>JSON</code>格式控制参数</li></ul>

<p><code>param</code>参数说明如下：</p>

<ul><li><code>mat</code>：矩阵变化参数，可对模型进行矩阵变化后导入，一般通过<code>ht.Default.createMatrix</code>函数构建变化矩阵</li><li><code>s3</code>：大小变化参数，格式为<code>[sx, sy, sz]</code></li><li><code>r3</code>：旋转变化参数，格式为<code>[rx, ry, rz]</code></li><li><code>rotationMode</code>：旋转模式参数，可取值以下值：<ul><li><code>xyz</code>：先进行<code>x</code>轴旋转，再进行<code>y</code>轴旋转，最后进行<code>z</code>轴旋转</li><li><code>xzy</code>：先进行<code>x</code>轴旋转，再进行<code>z</code>轴旋转，最后进行<code>y</code>轴旋转</li><li><code>yxz</code>：先进行<code>y</code>轴旋转，再进行<code>x</code>轴旋转，最后进行<code>z</code>轴旋转</li><li><code>yzx</code>：先进行<code>y</code>轴旋转，再进行<code>z</code>轴旋转，最后进行<code>x</code>轴旋转</li><li><code>zxy</code>：先进行<code>z</code>轴旋转，再进行<code>x</code>轴旋转，最后进行<code>y</code>轴旋转</li><li><code>zyx</code>：先进行<code>z</code>轴旋转，再进行<code>y</code>轴旋转，最后进行<code>x</code>轴旋转</li></ul></li><li><code>t3</code>：位置变化参数，格式为<code>[tx, ty, tz]</code>        </li><li><code>center</code>：模型是否居中，默认为<code>false</code>，设置为<code>true</code>则会移动模型位置使其内容居中</li><li><code>cube</code>：是否将模型缩放到单位<code>1</code>的尺寸范围内，默认为<code>false</code></li><li><code>ignoreMtls</code>：忽略部分材质，默认为空代表都读取，格式为<code>[&#39;material2&#39;, &#39;material3&#39;]</code>数组，忽略的材质不出现在返回值中</li><li><code>ignoreTransparent</code>：忽略材质透明度<code>d</code>属性，默认为<code>false</code>代表读取<code>d</code>值，读取该属性将影响返回值的<code>transparent</code>和<code>opacity</code>属性</li><li><code>ignoreColor</code>：忽略材质颜色<code>kd</code>属性，默认为<code>false</code>代表读取<code>kd</code>值，该属性在返回值中名称为<code>color</code></li><li><code>ignoreImage</code>：忽略材质贴图<code>map_kd</code>属性，默认为<code>false</code>代表读取<code>map_kd</code>值，该属性在返回值中名称为<code>image</code></li><li><code>ignoreNormal</code>：忽略法线向量，默认为<code>false</code>会读取法线向量信息，设置为<code>true</code>则忽略不读取法线向量信息</li><li><code>prefix</code>：图片路径前缀，即在<code>map_kd</code>值之前增加的前缀，如果是相对路径则以加载<code>obj</code>的<code>html</code>页面的路径为参考</li><li><code>flipY</code>：图片上下翻转，默认为<code>false</code>，遇到模型图片上下颠倒的情况可设置该参数为<code>true</code></li><li><code>flipFace</code>：默认为<code>false</code>，设置为<code>true</code>则代表翻转所有模型的面，也就是原来的正面变成反面，原来的反面变成正面 </li><li><code>reverseFlipMtls</code>：模型反面显示正面一样的内容，传入<code>*</code>代表适用于全部材质，传入<code>[&#39;material2&#39;, &#39;material3&#39;]</code>数组格式代表适用于指定材质</li><li><code>shape3d</code>：如果指定了<code>shape3d</code>名称，则<code>HT</code>将自动将加载解析后的所有材质模型构建成数组的方式，以该名称进行注册</li></ul>

<p><iframe src="examples/example_equipment.html" style="height:350px"></iframe></p>

<p><code>MTL</code>格式还有诸多参数，目前<code>HT</code>仅支持<code>d</code>、<code>kd</code>和<code>map_kd</code>这三个分别代表透明度、颜色和贴图的参数，
关于<code>OBJ</code>和<code>MTL</code>的格式标准可参考<a href="http://www.martinreddy.net/gfx/3d/OBJ.spec">这里</a>。</p>

<pre><code>map_Kd -o 0.1000 0.1200 0.0000 -s 45.0000 20.0000 0.0000 project/images/floor.jpg</code></pre>

<p>如上所示的贴图参数<code>map_Kd</code>，其中<code>-o</code>相当于<code>uv.offset</code>的贴图偏移参数；<code>-s</code>相当于<code>uv.scale</code>的贴图倍数参数。
对于<code>-o</code>和<code>-s</code>这两个属性<code>HT</code>只读取前面两个参数，忽略第三个参数。图片路径会自动增加<code>prefix</code>参数的前缀，
如果图片为相对路径，是相对于最终运行<code>html</code>页面的相对路径，也可以设置成注册到<code>ht.Default.setImage(name, ...)</code>中的图片名</p>

<p><img src=""></p>

<p>如果通过<code>ignoreNormal</code>设置为<code>true</code>忽略法线向量，或导出的<code>OBJ</code>文件不包含法线向量信息时，<code>HT</code>会自动构建对应<code>ns</code>法线向量信息，
但为实现特殊的表面效果，或通过较少的顶点实现平滑的界面渲染效果时常需要指定每个顶点的法线向量，
可参考<a href="http://en.wikipedia.org/wiki/Phong_shading">Phong shading</a>进行理解，
概述章节<a href="#ref_overview">例子</a>可发现，<code>ignoreNormal</code>设置为<code>true</code>的模型表面较为突兀棱角分明，不如读取法线向量的模型平滑。</p>

<p>模型几何变换顺序分别为：<code>mat</code> -&gt; <code>s3</code> -&gt; <code>r3</code> -&gt; <code>t3</code> -&gt; <code>center</code> -&gt; <code>cube</code>。
一般设置<code>s3</code>、<code>r3</code>和<code>t3</code>就能满足大部分需求，如需更复杂的矩阵变换，可通过<code>ht.Default.createMatrix</code>函数构建出<code>mat</code>矩阵参数，
采用<code>mat</code>参数的情况常用于模型形状位置需要与数据模型值绑定的情况：</p>

<p><iframe src="examples/example_meter.html" style="height:400px"></iframe></p>

<pre><code>modelMap.pointer.mat = {
    func: function(data){
        var start = Math.PI * 0.736,
            range = Math.PI * 1.46,   
            angle = start - range * data.a(&#39;value&#39;) / 100;
        return ht.Default.createMatrix([
            { t3: [0, -75, 0] },
            { r3: [Math.PI/4, 0, 0] },
            { r3: [0, 0, angle] },
            { r3: [-Math.PI/4, 0, 0] },
            { t3: [0, 75, 0]  }
        ]);
    }
};</code></pre>

<p>以上代码意思是对<code>pointer</code>的表计指针模型，先通过<code>t3: [0, -75, 0]</code>沿着<code>y</code>轴向下移动<code>75</code>，使得指针旋转点位于坐标原点上，
之后进行<code>r3: [Math.PI/4, 0, 0]</code>沿着<code>x</code>轴旋转<code>Math.PI/4</code>的弧度，使得指针直立在<code>xy</code>平面上，
然后进行<code>r3: [0, 0, angle]</code>沿着<code>z</code>轴旋转<code>angle</code>值的角度，最后再通过<code>r3: [-Math.PI/4, 0, 0]</code>和<code>t3: [0, 75, 0]</code>
将指针旋转和移动回原始位置，从而实现指针旋转角度与<code>data.a(&#39;meter.value&#39;)</code>的数据绑定。</p>

<blockquote><p>通过<code>ht.Default.parseObj</code>解析后的<code>map</code>结构<code>json</code>对象中，每个材质名对应一个模型信息，如果<code>cube</code>参数为<code>true</code>，
则返回的每个模型信息上将具有<code>rawS3</code>的特殊参数，同时传入的<code>param</code>参数也会增加<code>rawS3</code>属性信息，
该值为进行单位立体化前所有模型组合的最大尺寸范围，因此每个模型中的<code>rawS3</code>值是一样的。</p></blockquote>

<div id="ref_importmodel"></div>

<h2>导入模型</h2>

<p>要将<code>OBJ</code>解析后的模型信息绑定到图元，需先调用 <a href="../../plugin/modeling/ht-modeling-guide.html">建模手册</a> 中
<a href="../../plugin/modeling/ht-modeling-guide.html#ref_register">模型注册</a>
章节介绍的<code>ht.Default.setShape3dModel(name, model)</code>函数进行注册，之后图元只需将<code>style</code>的<code>shape3d</code>属性设置为注册的名称。</p>

<p><iframe src="examples/example_objajax.html" style="height:320px"></iframe></p>

<p>以上例子构建了两辆摩托车模型，他们都读取至相同的<code>OBJ</code>文件信息，名称为<code>Separate Scooter</code>的摩托车，是有由一堆的<code>Node</code>，
相互<code>host</code>吸附成环状，当用户拖动旋转操作时感觉像一个整体，这种方式下每个部分可独立选中，染色和隐藏等操作。</p>

<p>如果需要整个摩托车模型上仅对应一个<code>Node</code>图元，则可采用例子中标注为<code>One Node</code>的摩托车方式，将所有材质对应的模型融合成一个<code>array</code>数组，
通过<code>ht.Default.setShape3dModel(&#39;scooter&#39;, array)</code>进行注册，这样<code>scooter</code>名称的模型将具有完整的<code>OBJ</code>模型信息，
参见建模手册的<a href="../../plugin/modeling/ht-modeling-guide.html#ref_group">模型组合</a>章节。</p>

<pre><code>for(var name in modelMap){                    
    var model = modelMap[name];   
    var shape3d = &#39;scooter:&#39; + name;

    ht.Default.setShape3dModel(shape3d, model);
    array.push(model);                    

    var node = new ht.Node();
    node.s({
        &#39;shape3d&#39;: shape3d
    });                                                               
    node.setHost(lastNode);
    ...
}

ht.Default.setShape3dModel(&#39;scooter&#39;, array);
var node = new ht.Node();
node.s(&#39;shape3d&#39;, &#39;scooter&#39;);                </code></pre>

<p><code>Node</code>的<code>s3</code>大小参数会影响模型的最终呈现效果，最终呈现的模型大小将由导入的<code>OBJ</code>几何模型大小乘以对应的<code>Node</code>的<code>s3</code>大小，
也就是说如果导入的<code>OBJ</code>模型的<code>rawS3</code>大小为<code>[10, 20, 30]</code>的尺寸，则如果<code>Node</code>的<code>s3</code>为<code>[10, 5, 3]</code>，
则最终呈现于界面的大小为<code>[10*10, 20*5, 30*3]</code>。</p>

<p>如果模型的大小不像受<code>Node</code>的<code>s3</code>的影响，可通过设置<code>shape3d.scaleable</code>为<code>false</code>，该值默认为<code>true</code>。
因此如果模型大小受<code>s3</code>影响的情况下，一般导入模型时设置<code>cube</code>参数为<code>true</code>，将导入的模型缩放到<code>[1,1,1]</code>单元立方体内，
然后将对应的<code>Node</code>图元的<code>s3</code>参数设置成解析后模型的<code>rawS3</code>参数，该参数代表<code>OBJ</code>模型在缩放到单元立方体之前的大小。</p>

<div id="ref_ajax"></div>

<h3>AJAX加载</h3>

<p>上例加载<code>OBJ</code>文件采用<code>AJAX</code>的方式，通过构建两个<code>XMLHttpRequest</code>对象，
通过<code>AJAX</code>方式分别获取<code>obj</code>和<code>mtl</code>文件，在数据<code>onload</code>之后再进行解析处理。</p>

<pre><code>load(&#39;obj/scooter.mtl&#39;, &#39;obj/scooter.obj&#39;);

function load(mtlUrl, objUrl){
    var xhr1 = new XMLHttpRequest();
    xhr1.onload = function(e){                
        var mtlText = e.target.responseText;
        var xhr2 = new XMLHttpRequest();
        xhr2.onload = function(e){
            var objText = e.target.responseText;
            parse(mtlText, objText);                                                                               
        };
        xhr2.open(&#39;GET&#39;, objUrl, true);
        xhr2.send(null);                     
    };                
    xhr1.open(&#39;GET&#39;, mtlUrl, true);
    xhr1.send(null);                                               
}</code></pre>

<div id="ref_loadobj"></div>

<h3>loadObj函数</h3>

<p>以下例子也实现了同样的功能，但采用了<code>HT</code>提供的<code>ht.Default.loadObj</code>更为便捷的函数。</p>

<p><iframe src="examples/example_loadobj.html" style="height:320px"></iframe></p>

<p><code>ht.Default.loadObj(objUrl, mtlUrl, params)</code>：</p>

<ul><li><code>objUrl</code>：<code>OBJ</code>文件路径</li><li><code>mtlUrl</code>：<code>MTL</code>文件路径</li><li><code>params</code>：<code>JSON</code>结构参数，可设置<code>ht.Default.parseObj(text, mtlMap, params)</code>第三个参数类型的控制信息，并增加以下参数<ul><li><code>sync</code>：是否同步参数，默认为<code>false</code>代表异步加载，设置为<code>true</code>代表同步加载，意味数据加载后才运行<code>loadObj</code>之后的代码    </li><li><code>finishFunc: function(modelMap, array, rawS3){}</code>：用于加载后的回调处理，<ul><li><code>modelMap</code>：调用<code>ht.Default.parseObj</code>解析后的返回值，若加载或解析失败则返回值为空</li><li><code>array</code>：所有材质模型组成的数组</li><li><code>rawS3</code>：包含所有模型的原始尺寸</li></ul></li></ul></li></ul>

<div id="ref_string"></div>

<h3>字符串嵌入</h3>

<p><a href="../../core/serialization/ht-serialization-guide.html">序列化</a>手册例子获取<code>OBJ</code>和<code>MTL</code>文本内容的方式较特殊，
将模型信息内容存放到<code>function</code>的注解中，将<code>function</code>转换成字符串，然后裁剪掉头尾取中间实际模型内容部分，
传统的方式一般将<code>.obj</code>和<code>.mtl</code>存放在服务端，客户端通过<code>AJAX</code>的方式分别加载文件内容进行解析，
采用截取注解字符串的方式，可避免跨域安全访问限制问题，浏览器直接本地打开即可运行。</p>

<pre><code>var scooter_mtl = getRawText(function(){/*

newmtl Black
Ns 190.196078
Ka 0.000000 0.000000 0.000000
Kd 0.000000 0.000000 0.000000
Ks 0.100000 0.100000 0.100000
Ni 1.000000
d 1.000000
illum 2
...

*/});

function getRawText(obj){
    var text = String(obj); 
    return text.substring(14, text.length-3);
} </code></pre>

<div id="ref_doorwindow"></div>

<h2>门窗应用</h2>

<p><code>OBJ</code>导入模型的另外一种应用就是与<a href="../../plugin/modeling/ht-modeling-guide.html#ref_csgnode">CSGNode</a>的结合，
特别是构建门窗等应用场景时，可用<a href="../../plugin/modeling/ht-modeling-guide.html#ref_csgnode">CSGNode</a>进行挖空，
将图元<code>style</code>的<code>shape3d</code>属性设置为<code>OBJ</code>导入注册的模型，使得图元显示为更逼真的<code>OBJ</code>门窗建模效果。</p>

<p>结合<a href="../../plugin/modeling/ht-modeling-guide.html#ref_doorwindow">DoorWindow</a>类型可实现开门开窗效果，
以下示例展示了<code>OBJ</code>导入模型与<a href="../../plugin/modeling/ht-modeling-guide.html#ref_csgnode">CSGNode</a>和
<a href="../../plugin/modeling/ht-modeling-guide.html#ref_doorwindow">DoorWindow</a>两种类型结合的门窗应用场景：</p>

<p><iframe src="examples/example_room.html" style="height:320px"></iframe></p>    <hr id="contact" style="margin: 20px 0 1px 0">
    <a href="http://www.hightopo.com"><img class="logo"></a>欢迎交流 <a href="mailto:service@hightopo.com">service@hightopo.com</a>
</body>
</html>
